﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Security;
using System.IO;
using System.Threading;
using System.Drawing;
using System.Drawing.Imaging;

namespace Swoosh.Utilities.TraceLogger
{
    public static class Logger
    {
        public static LogLevels ConsoleLogLevel = LogLevels.Info;
        public static LogLevels FileLogLevel = LogLevels.Info;
        public static LogLevels EventLogLevel = LogLevels.Warn;

        public static string FileLogPath = "";
        public static string FileLogName = "swoosh";

        public static ExceptionsInLogMessagesType LogShowException = ExceptionsInLogMessagesType.MessageAndStackTrace;

        public static void Initialize()
        {
            Trace.Listeners.Clear();
            ConsoleTraceListenerExtension ctl = new ConsoleTraceListenerExtension();
            Trace.Listeners.Add(ctl);
            FileTraceListenerExtension ftle = new FileTraceListenerExtension(FileLogName, FileLogPath);
            Trace.Listeners.Add(ftle);
            //EventLogListenerExtension elle = new EventLogListenerExtension();
            //Trace.Listeners.Add(elle);
        }

        public static void Log(LogLevels level, string message)
        {
            Trace.WriteLine(message, level.ToString());
        }

        public static void Log(LogLevels log_level, string log_message, Exception exception)
        {
            switch (LogShowException)
            {
                case ExceptionsInLogMessagesType.Message:
                    Log(log_level, string.Format("{0}\n\n{1}: {2}\n", log_message, exception.GetType().ToString(), exception.Message));
                    break;
                case ExceptionsInLogMessagesType.MessageAndStackTrace:
                    if (exception != null)
                    {
                        if (exception.InnerException != null)
                        {
                            Log(log_level, string.Format("{0}\n\n{1}\n\nInner Exception:\n{2}\n", log_message, exception.ToString(), exception.InnerException.ToString()));
                        }
                        else
                        {
                            Log(log_level, string.Format("{0}\n\n{1}\n", log_message, exception.ToString()));
                        }
                    }
                    else
                    {
                        Log(log_level, string.Format("{0}\n", log_message));
                    }
                    break;
                case ExceptionsInLogMessagesType.No:
                default:
                    Log(log_level, string.Format("{0}\n", log_message));
                    break;
            }
        }

        public static void LogBitmap(LogLevels log_level, Bitmap log_image)
        {
            lock (FileLogPath)
            {
                if (!Directory.Exists(Path.Combine(FileLogPath, "Images")))
                    Directory.CreateDirectory(Path.Combine(FileLogPath, "Images"));

                string filename = Path.Combine(Path.Combine(FileLogPath, "Images"), DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss.fff") + "_" + FileLogName + ".png");

                if (!File.Exists(filename))
                {
                    Log(log_level, "LogImage: " + filename);
                    log_image.Save(filename, ImageFormat.Png);
                }
                else
                {
                    Thread.Sleep(50);
                    filename = Path.Combine(Path.Combine(FileLogPath, "Images"), DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss.fff") + "_" + FileLogName + ".png");
                    Log(log_level, "LogImage: " + filename);
                    log_image.Save(filename, ImageFormat.Png);
                }
            }
        }
    }

    #region LogExtensions

    public class ConsoleTraceListenerExtension : ConsoleTraceListener
    {
        public static object ConsoleLoggerLock = new object();

        public override void WriteLine(string message, string category)
        {
            lock (ConsoleTraceListenerExtension.ConsoleLoggerLock)
            {
                LogLevels ll;
                if (Enum.TryParse<LogLevels>(category, true, out ll))
                {
                    if (Logger.ConsoleLogLevel >= ll)
                    {
                        Console.Out.WriteLine("(" + Thread.CurrentThread.ManagedThreadId + ")" + ll.ToString().ToUpper() + ": " + message);
                    }
                }
            }
        }
        public override void WriteLine(string message)
        {
            WriteLine(message, LogLevels.Info.ToString());
        }

        public override void Write(string message)
        {
            lock (ConsoleTraceListenerExtension.ConsoleLoggerLock)
            {
                string str = message.Trim();

                if (!string.IsNullOrEmpty(str))
                {
                    Console.Out.Write(str);
                }
            }
        }
    }

    public class FileTraceListenerExtension : TraceListener
    {
        private string FileName;
        private string FilePath;

        public static object FileLoggerLock = new object();

        public FileTraceListenerExtension(string name, string path)
            : base()
        {
            FileName = name;
            FilePath = path;

            if (!Directory.Exists(FilePath))
            {
                Directory.CreateDirectory(FilePath);
            }
        }

        public override void Write(string message)
        {
            lock (FileTraceListenerExtension.FileLoggerLock)
            {
                string str = message.Trim();

                if (!string.IsNullOrEmpty(str))
                {
                    string logFile = Path.Combine(FilePath, DateTime.Today.ToString("yyyy-MM-dd") + "_" + FileName + ".log");
                    StreamWriter log;

                    if (!File.Exists(logFile))
                    {
                        log = new StreamWriter(logFile);
                    }
                    else
                    {
                        log = File.AppendText(logFile);
                    }

                    log.Write(str);
                    log.Close();
                }
            }
        }

        public override void WriteLine(string message, string category)
        {
            lock (FileTraceListenerExtension.FileLoggerLock)
            {
                LogLevels ll;
                if (Enum.TryParse<LogLevels>(category, true, out ll))
                {
                    if (Logger.FileLogLevel >= ll)
                    {
                        string logFile = Path.Combine(FilePath, DateTime.Today.ToString("yyyy-MM-dd") + "_" + FileName + ".log");
                        StreamWriter log;

                        if (!File.Exists(logFile))
                        {
                            log = new StreamWriter(logFile);
                        }
                        else
                        {
                            log = File.AppendText(logFile);
                        }

                        log.WriteLine("-------------------------------------------------------------------------------------");
                        log.WriteLine(DateTime.Now + " - (Thread-ID: " + Thread.CurrentThread.ManagedThreadId + ") - " + ll.ToString().ToUpper() + ": " + message);
                        log.Close();
                    }
                }
            }
        }
        public override void WriteLine(string message)
        {
            WriteLine(message, LogLevels.Info.ToString());
        }
    }

    public class EventLogListenerExtension : TraceListener
    {
        private string eventLogSource = "Swoosh";
        private EventLog eventLog;

        public static object EventLoggerLock = new object();

        public EventLogListenerExtension()
            : base()
        {
            
            try
            {
                if (Logger.EventLogLevel != LogLevels.Disable)
                {
                    eventLog = new EventLog();
                    bool bRet = false;
                    try
                    {
                        bRet = EventLog.SourceExists(eventLogSource);
                    }
                    catch (SecurityException)
                    {
                        System.Diagnostics.Trace.WriteLine("Not enough credentials to create an event source - switching to Application.");
                        bRet = true;
                        eventLogSource = "Application";
                    }
                    catch (Exception ex)
                    {
                        System.Diagnostics.Trace.WriteLine("Unexpected exception while checking for an event source: " + ex.ToString());
                        bRet = true;
                        eventLogSource = "Application";
                    }
                    if (!bRet)
                    {
                        try
                        {
                            EventLog.CreateEventSource(eventLogSource, "Application");
                        }
                        catch (SecurityException)
                        {
                            System.Diagnostics.Trace.WriteLine("Not enough credentials to create an event source - switching to Application.");
                            eventLogSource = "Application";
                        }
                        catch (Exception ex)
                        {
                            System.Diagnostics.Trace.WriteLine("Unexpected exception while creating an event source: " + ex.ToString());
                            eventLogSource = "Application";
                        }
                    }

                    eventLog.Source = eventLogSource;
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(ex.ToString());
            }
        }

        public override void Write(string message)
        {
            string str = message.Trim();

            // Just in case - log the event if it's not a Trace header
            if (str[str.Length - 1] != ':')
            {
                WriteLine(str);
            }
        }

        public override void WriteLine(string message)
        {
            WriteLine(message, LogLevels.Info.ToString());
        }

        public override void WriteLine(string message, string category)
        {
            lock (EventLogListenerExtension.EventLoggerLock)
            {
                LogLevels ll;
                if (Enum.TryParse<LogLevels>(category, true, out ll))
                {
                    EventLogEntryType entryType;
                    if (ll == LogLevels.Debug || ll == LogLevels.Info || ll == LogLevels.Trace)
                        entryType = EventLogEntryType.Information;
                    else if (ll == LogLevels.Warn)
                        entryType = EventLogEntryType.Warning;
                    else
                        entryType = EventLogEntryType.Error;

                    if (Logger.EventLogLevel >= ll)
                    {
                        eventLog.WriteEntry(ll.ToString().ToUpper() + ": " + message, entryType);
                    }
                }
            }

        }
    }

    #endregion

    public enum ExceptionsInLogMessagesType
    {
        No, Message, MessageAndStackTrace
    }

    public enum LogLevels
    {
        Disable = -1, Fatal = 0, Error = 1, Warn = 2, Info = 3, Debug = 4, Trace = 5
    }
}
